package jp.gnt.swf;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Map;
import java.util.Stack;

import javax.imageio.ImageIO;
import org.apache.commons.codec.binary.Base64;

public class SWFService {

	/**
	 * @param bytes
	 * @param size
	 *            (size < 8)
	 * @return
	 */
	public static long convertByte2Long(byte[] bytes, int size) {
		long buffer = 0x00L;

		for (int i = size - 1; i >= 0; i--) {
			buffer |= (bytes[i] & 0xffL) << (8 * i);
		}

		return buffer;
	}

	/**
	 * @param bytes
	 * @param size
	 *            (size < 4)
	 * @return
	 */
	public static int convertByte2Int(byte[] bytes, int size) {
		int buffer = 0x00;

		for (int i = size - 1; i >= 0; i--) {
			buffer |= (bytes[i] & 0x000000FF) << (8 * i);
		}

		return buffer;
	}

	/**
	 * 
	 * @param var
	 * @return
	 */
	public static byte[] convertLong2Byte(long var) {
		byte[] bytes = new byte[8];

		for (int i = 0; i < 8; i++) {
			bytes[i] = (byte) ((var >> 8 * i) & 0x000000FFL);
		}

		return bytes;
	}

	/**
	 * 
	 * @param var
	 * @return
	 */
	public static byte[] convertInt2Byte(int var) {
		byte[] bytes = new byte[4];

		for (int i = 0; i < 4; i++) {
			bytes[i] = (byte) ((var >> 8 * i) & 0x000000FF);
		}

		return bytes;
	}

	/**
	 * 
	 * @param bytes
	 * @return
	 */
	public static int[] getTagCodeAndLength(byte[] bytes) {
		int[] fields = new int[2];

		int buffer = (bytes[0] & SWFUtils.maskInt) + ((bytes[1] & SWFUtils.maskInt) << 8);

		fields[0] = buffer >> 6;
		fields[1] = buffer & 0x3f;

		return fields;
	}

	/**
	 * 
	 * @param ins
	 * @return
	 * @throws IOException
	 */
	public static byte[] getByteArray(String pathName) throws IOException {
		int byteLength = 0;
		byte[] buffer = new byte[SWFUtils.BUFFER_SIZE];

		FileInputStream fins = new FileInputStream(new File(pathName));
		ByteArrayOutputStream outs = new ByteArrayOutputStream();

		while ((byteLength = fins.read(buffer, 0, SWFUtils.BUFFER_SIZE)) != -1) {
			outs.write(buffer, 0, byteLength);
		}

		fins.close();
		outs.flush();
		return outs.toByteArray();
	}

	/**
	 * 
	 * @param params
	 * @param actionBytes
	 * @param dataLength
	 * @return
	 * @throws IOException
	 */
	public static byte[] repairActionParams(Map<String, String> params, byte[] actionBytes, int dataLength)
			throws IOException {
		byte[] buffer = new byte[SWFUtils.BUFFER_SIZE];
		ByteArrayInputStream ins = new ByteArrayInputStream(actionBytes, 0, dataLength);
		ByteArrayOutputStream outs = new ByteArrayOutputStream();
		Stack<String> stack = new Stack<String>();

		while (ins.read(buffer, 0, 1) != -1) {
			int dataActionLength = 0;

			// Read action code //
			int actionCode = buffer[0] & SWFUtils.maskInt;

			// Check the action carries data //
			if (actionCode > 0x80) {
				ins.read(buffer, 0, 2);
				dataActionLength = (int) SWFService.convertByte2Int(buffer, 2);
			}

			switch (actionCode) {
			case SWFUtils.ACTION_CODE.ACTION_PUSH:
				// Read type of data //
				ins.read(buffer, 0, 1);
				int dataType = buffer[0] & SWFUtils.maskInt;
				dataActionLength -= 1;

				switch (dataType) {
				case SWFUtils.DATA_TYPE.STRING:
					// Read data content //
					ins.read(buffer, 0, dataActionLength);
					// Push data to stack //
					// The last byte is zero //
					String temp = new String(buffer, 0, dataActionLength - 1);
					stack.push(temp);
					break;
				default:
					// Not take care if not string type
					ins.read(buffer, 0, dataActionLength);
					outs.write(convertActionCodeAndLength(actionCode, dataActionLength));
					outs.write(dataType);
					outs.write(buffer, 0, dataActionLength);
				}
				break;
			case SWFUtils.ACTION_CODE.ACTION_SET_VARIABLE:
				String paramName = null;
				String paramValue = null;

				if (stack.size() == 2) {
					paramValue = stack.pop(); // Get value //
					paramName = stack.pop(); // Get name //
					if (params.containsKey(paramName)) {
						paramValue = params.get(paramName) == "" ? "null" : params.get(paramName);
					}
					// Write ActionPush data //
					outs.write(getByteActionPush(paramName));
					outs.write(getByteActionPush(paramValue));
				}

				// ACTION_SET_VARIABLE //
				outs.write(SWFUtils.ACTION_CODE.ACTION_SET_VARIABLE);
				break;
			default:
				// Write all elements in stack //
				if (stack.size() > 0) {
					for (int i = 0; i < stack.size(); i++) {
						outs.write(getByteActionPush(stack.elementAt(i)));
					}
					stack.removeAllElements();
				}

				// Write current action //
				outs.write(convertActionCodeAndLength(actionCode, dataActionLength));
				if (dataActionLength > 0) {
					ins.read(buffer, 0, dataActionLength);
					outs.write(buffer, 0, dataActionLength);
				}
			}
		}

		return outs.toByteArray();
	}

	/**
	 * 
	 * @param value
	 * @return
	 * @throws IOException
	 */
	public static byte[] getByteActionPush(String value) throws IOException {
		ByteArrayOutputStream outs = new ByteArrayOutputStream();

		outs.write(convertActionCodeAndLength(SWFUtils.ACTION_CODE.ACTION_PUSH, value.getBytes().length + 2));
		// Data type //
		outs.write(0x00);
		outs.write(value.getBytes());
		// null-terminate //
		outs.write(0x00);

		return outs.toByteArray();
	}

	/**
	 * 
	 * @param actionCode
	 * @param dataActionLength
	 * @return
	 */
	public static byte[] convertActionCodeAndLength(int actionCode, int dataActionLength) {
		ByteArrayOutputStream outs = new ByteArrayOutputStream();

		outs.write(actionCode);

		if (actionCode > 0x80) {
			byte[] int2Byte = SWFService.convertInt2Byte(dataActionLength);
			outs.write(int2Byte, 0, 2);
		}

		return outs.toByteArray();
	}

	/**
	 * 
	 * @param imagePath
	 * @param tagCode
	 * @return
	 * @throws IOException
	 */
	public static byte[] getDataImage(String imagePath, int tagCode) throws IOException {
		byte[] byteArrays = null;

		long before = Calendar.getInstance().getTimeInMillis();

		switch (tagCode) {
		case SWFUtils.TAG_CODE.DEFINE_BITS_LOSSLESS:
		case SWFUtils.TAG_CODE.DEFINE_BITS_LOSSLESS2:
			byteArrays = getLosslessDataImage(imagePath);
			break;
		case SWFUtils.TAG_CODE.DEFINE_BITS_JPEG2:
			byteArrays = SWFService.getByteArray(imagePath);
			break;
		case SWFUtils.TAG_CODE.DEFINE_BITS_JPEG3:
			byteArrays = getDefineBitJPEG3(imagePath);
			break;
		}

		long after = Calendar.getInstance().getTimeInMillis();
		System.out.println("============getImage: " + (after - before) + " -> tag:" + tagCode);

		return byteArrays;
	}

	/**
	 * 
	 * @param imagePath
	 * @return
	 * @throws IOException
	 */
	public static byte[] getDefineBitJPEG3(String imagePath) throws IOException {
		ByteArrayOutputStream _outs = new ByteArrayOutputStream();
		Base64 bs64 = new Base64();

		BufferedImage image = ImageIO.read(new File(imagePath));
		int width = image.getWidth();
		int height = image.getHeight();

		int offset = 0;
		byte[] bitmapAlphaData = new byte[width * height];

		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int pixel = image.getRGB(x, y);
				bitmapAlphaData[offset++] = (byte) ((pixel >> 24) & 0xff); // Alpha
																			// pixel
			}
		}
		Compressor compressor = new Compressor();
		byte[] bitmapPixelDataCompressed = compressor.compress(bitmapAlphaData);
		byte[] totalByteImage = SWFService.getByteArray(imagePath);

		_outs.write(convertInt2Byte(totalByteImage.length));

		String dataImage = bs64.encodeToString(totalByteImage);
		_outs.write(Base64.decodeBase64(dataImage));

		_outs.write(bitmapPixelDataCompressed);

		return _outs.toByteArray();
	}

	/**
	 * Read n bytes from stream {@link InputStream}
	 */
	public static byte[] readBytes(InputStream ins, long dataLength) throws IOException {
		byte[] buffer = new byte[SWFUtils.BUFFER_SIZE];
		ByteArrayOutputStream outs = new ByteArrayOutputStream();

		int readNumOfByte = (int) (dataLength > 0 ? (dataLength > SWFUtils.BUFFER_SIZE ? SWFUtils.BUFFER_SIZE
				: dataLength) : 0);

		while (readNumOfByte > 0) {
			ins.read(buffer, 0, readNumOfByte);
			outs.write(buffer, 0, readNumOfByte);
			dataLength -= readNumOfByte;
			readNumOfByte = (int) (dataLength > 0 ? (dataLength > SWFUtils.BUFFER_SIZE ? SWFUtils.BUFFER_SIZE
					: dataLength) : 0);
		}

		return outs.toByteArray();
	}

	/**
	 * @param url
	 * @return data image (DefineBitsLossless & DefineBitsLossless2)
	 * @throws IOException
	 */
	public static byte[] getLosslessDataImage(String url) throws IOException {
		BufferedImage image = ImageIO.read(new File(url));

		int width = image.getWidth();
		int height = image.getHeight();

		int offset = 0;
		byte[] bitmapPixelData = new byte[width * height * 4];

		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int pixel = image.getRGB(x, y);
				// ARGB
				bitmapPixelData[offset++] = (byte) ((pixel >> 24) & 0xff);
				bitmapPixelData[offset++] = (byte) ((pixel >> 16) & 0xff);
				bitmapPixelData[offset++] = (byte) ((pixel >> 8) & 0xff);
				bitmapPixelData[offset++] = (byte) ((pixel) & 0xff);
			}
		}

		ByteArrayOutputStream _outs = new ByteArrayOutputStream();

		Compressor compressor = new Compressor();
		byte[] bitmapPixelDataCompressed = compressor.compress(bitmapPixelData);

		_outs.write(0x05); // BitmapFormat = 5

		byte[] bytesWidth = convertInt2Byte(width);
		_outs.write(bytesWidth, 0, 2); // BitmapWidth

		byte[] bytesHeight = convertInt2Byte(height);
		_outs.write(bytesHeight, 0, 2); // BitmapWidth

		_outs.write(bitmapPixelDataCompressed);

		return _outs.toByteArray();
	}

	/**
	 * @param tagCode
	 * @return byte[]
	 * @throws IOException
	 */
	public static byte[] revertTagCodeAndLength(int tagCode, long length, boolean longTagHeader) throws IOException {
		ByteArrayOutputStream byteArrays = new ByteArrayOutputStream();

		if (longTagHeader) {
			int temp = tagCode << 6;
			temp += 0x3F;

			byteArrays.write(temp & SWFUtils.maskInt);
			byteArrays.write((temp >> 8) & SWFUtils.maskInt);

			byte[] long2Byte = convertLong2Byte(length);
			byteArrays.write(long2Byte, 0, 4);
		} else {
			int temp = tagCode << 6;
			temp += length;

			byteArrays.write(temp & SWFUtils.maskInt);
			byteArrays.write((temp >> 8) & SWFUtils.maskInt);
		}

		return byteArrays.toByteArray();
	}
}
